package drds.plus.datanode.api;

import drds.plus.common.jdbc.SetParameterMethod;
import drds.plus.common.jdbc.SetParameterMethodAndArgs;
import drds.plus.common.jdbc.SqlTypeParser;
import drds.plus.common.model.SqlType;
import drds.plus.datanode.configuration.DataSourceIndexAndNeedRetryIfDailed;
import drds.plus.datanode.configuration.DataSourceWrapper;
import drds.plus.datanode.executor.AbstractExecutor;
import drds.plus.datanode.executor.Executor;
import drds.plus.datasource.api.ConnectionWrapper;
import drds.plus.datasource.api.PreparedStatementWrapper;
import drds.plus.datasource.api.ResultSetWrapper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.*;
import java.util.*;

@Slf4j
public class PreparedStatement extends Statement {

    // 参数列表到参数上下文的映射 如 1:id 2：'2011-11-11'
    @Setter
    @Getter
    protected Map<Integer, SetParameterMethodAndArgs> indexToSetParameterMethodAndArgsMap = new HashMap<Integer, SetParameterMethodAndArgs>();
    @Setter
    @Getter
    private String sql;
    @Setter
    @Getter
    private int autoGeneratedKeys = -1;
    @Setter
    @Getter
    private List<Map<Integer, SetParameterMethodAndArgs>> indexToSetParameterMethodAndArgsMapList;

    private Executor<Integer> executeUpdateExecutor = new AbstractExecutor<Integer>() {

        public Integer createConnectionWrapperAndExecute(DataSourceWrapper dataSourceWrapper, Object... args) throws SQLException {
            ConnectionWrapper connectionWrapper = PreparedStatement.this.connection.createConnectionWrapper(dataSourceWrapper, false);
            return executeUpdate(connectionWrapper);
        }
    };
    private Executor<int[]> executeBatchExecutor = new AbstractExecutor<int[]>() {

        public int[] createConnectionWrapperAndExecute(DataSourceWrapper dataSourceWrapper, Object... args) throws SQLException {
            ConnectionWrapper connectionWrapper = connection.createConnectionWrapper(dataSourceWrapper, false);
            return executeBatch(connectionWrapper);
        }
    };

    public PreparedStatement(DatasourceManager datasourceManager, Connection connection, String sql, String applicationId) {
        super(applicationId, datasourceManager, connection);
        this.sql = sql;
    }


    private PreparedStatementWrapper createPreparedStatementWrapper(ConnectionWrapper connectionWrapper, String sql) throws SQLException {
        PreparedStatementWrapper preparedStatementWrapper;
        if (autoGeneratedKeys != -1) {
            preparedStatementWrapper = connectionWrapper.prepareStatement(sql, autoGeneratedKeys);
        } else {
            preparedStatementWrapper = connectionWrapper.prepareStatement(sql);
        }
        setStatementWrapper(preparedStatementWrapper);
        preparedStatementWrapper.setQueryTimeout(queryTimeout); // 这句可能抛出异常，所以要放到setBaseStatement之后
        preparedStatementWrapper.setFetchSize(fetchSize);
        preparedStatementWrapper.setMaxRows(maxRows);
        return preparedStatementWrapper;
    }

    private static void setBatchParameters(java.sql.PreparedStatement preparedStatement, Collection<SetParameterMethodAndArgs> batchedParameters) throws SQLException {
        for (SetParameterMethodAndArgs setParameterMethodAndArgs : batchedParameters) {
            setParameterMethodAndArgs.getSetParameterMethod().setParameter(preparedStatement, setParameterMethodAndArgs.getArgs());
        }
    }

    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setBigDecimal, new Object[]{parameterIndex, x}));
    }

    public void setClob(int parameterIndex, Clob x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setClob, new Object[]{parameterIndex, x}));
    }

    public void setDate(int parameterIndex, Date x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setDate, new Object[]{parameterIndex, x}));
    }

    public void setDouble(int parameterIndex, double x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setDouble, new Object[]{parameterIndex, x}));
    }

    public void setFloat(int parameterIndex, float x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setFloat, new Object[]{parameterIndex, x}));
    }

    public void setInt(int parameterIndex, int x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setInt, new Object[]{parameterIndex, x}));
    }

    public void setLong(int parameterIndex, long x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setLong, new Object[]{parameterIndex, x}));
    }

    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setNull1, new Object[]{parameterIndex, sqlType}));
    }

    public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(paramIndex, new SetParameterMethodAndArgs(SetParameterMethod.setNullWithTypeName, new Object[]{paramIndex, sqlType, typeName}));
    }

    public void setObject(int parameterIndex, Object x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setObject, new Object[]{parameterIndex, x}));
    }

    public void setString(int parameterIndex, String x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setString, new Object[]{parameterIndex, x}));
    }

    public void setTime(int parameterIndex, Time x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setTime, new Object[]{parameterIndex, x}));
    }

    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {

    }

    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        indexToSetParameterMethodAndArgsMap.put(parameterIndex, new SetParameterMethodAndArgs(SetParameterMethod.setTimestamp, new Object[]{parameterIndex, x}));
    }

    //
    public boolean execute() throws SQLException {
        if (SqlTypeParser.isQuerySql(sql)) {
            executeQuery();
            return true;
        } else {
            super.updateCount = executeUpdate();
            return false;
        }
    }

    public ResultSetWrapper executeQuery() throws SQLException {
        checkClosed();
        ensureResultSetWrapperIsEmpty();
        boolean read = SqlType.SELECT.equals(SqlTypeParser.getSqlType(sql)) && connection.getAutoCommit();
        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper(sql, read);
        if (connectionWrapper != null) {
            sql = DataNodeHintParser.removeDataNodeIdHint(sql);
            return executeQuery(connectionWrapper, sql);
        } else {
            // hint优先
            DataSourceIndexAndNeedRetryIfDailed dataSourceIndexAndNeedRetryIfDailed = DataNodeHintParser.convertHint2Index(sql);
            sql = DataNodeHintParser.removeDataNodeIdHint(sql);
            if (dataSourceIndexAndNeedRetryIfDailed == null) {
                dataSourceIndexAndNeedRetryIfDailed = ThreadLocalDataSourceIndex.getIndex();
            }
            return datasourceManager.getSelector(read).getConnectionWrapperAndExecuteAndRetryIfFailed(executeQueryExecutor, retryingTimes, sql, dataSourceIndexAndNeedRetryIfDailed);
        }
    }

    protected ResultSetWrapper executeQuery(ConnectionWrapper connectionWrapper, String sql) throws SQLException {
        PreparedStatementWrapper preparedStatementWrapper = createPreparedStatementWrapper(connectionWrapper, sql);
        java.sql.PreparedStatement preparedStatement = (java.sql.PreparedStatement) preparedStatementWrapper.statement;
        SetParameterMethodAndArgs.setParameters(preparedStatement, indexToSetParameterMethodAndArgsMap);
        this.resultSetWrapper = preparedStatementWrapper.executeQuery();
        return this.resultSetWrapper;
    }

    //
    public int executeUpdate() throws SQLException {
        checkClosed();
        ensureResultSetWrapperIsEmpty();
        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper(sql, false);
        if (connectionWrapper != null) {
            sql = DataNodeHintParser.removeDataNodeIdHint(sql);
            int updateCount = executeUpdate(connectionWrapper);
            super.updateCount = updateCount;
            return updateCount;
        } else {
            // hint优先
            DataSourceIndexAndNeedRetryIfDailed dataSourceIndexAndNeedRetryIfDailed = DataNodeHintParser.convertHint2Index(sql);
            sql = DataNodeHintParser.removeDataNodeIdHint(sql);
            if (dataSourceIndexAndNeedRetryIfDailed == null) {
                dataSourceIndexAndNeedRetryIfDailed = ThreadLocalDataSourceIndex.getIndex();
            }
            int updateCount = datasourceManager.getSelector(false).getConnectionWrapperAndExecuteAndRetryIfFailed(null, executeUpdateExecutor, retryingTimes, sql, dataSourceIndexAndNeedRetryIfDailed);
            super.updateCount = updateCount;
            return updateCount;
        }
    }

    private int executeUpdate(ConnectionWrapper connectionWrapper) throws SQLException {
        PreparedStatementWrapper preparedStatementWrapper = createPreparedStatementWrapper(connectionWrapper, sql);
        java.sql.PreparedStatement preparedStatement = (java.sql.PreparedStatement) preparedStatementWrapper.statement;
        SetParameterMethodAndArgs.setParameters(preparedStatement, indexToSetParameterMethodAndArgsMap);
        return preparedStatementWrapper.executeUpdate();
    }

    public void addBatch() throws SQLException {
        if (indexToSetParameterMethodAndArgsMapList == null) {
            indexToSetParameterMethodAndArgsMapList = new LinkedList<Map<Integer, SetParameterMethodAndArgs>>();
        }
        Map<Integer, SetParameterMethodAndArgs> indexToSetParameterMethodAndArgsMapCopy = new HashMap<Integer, SetParameterMethodAndArgs>(indexToSetParameterMethodAndArgsMap.size());
        indexToSetParameterMethodAndArgsMapCopy.putAll(indexToSetParameterMethodAndArgsMap);
        indexToSetParameterMethodAndArgsMap.clear();
        indexToSetParameterMethodAndArgsMapList.add(indexToSetParameterMethodAndArgsMapCopy);
    }

    public int[] executeBatch() throws SQLException {
        try {
            checkClosed();
            ensureResultSetWrapperIsEmpty();
            if (indexToSetParameterMethodAndArgsMapList == null || indexToSetParameterMethodAndArgsMapList.isEmpty()) {
                return new int[0];
            }
            ConnectionWrapper connectionWrapper = connection.getConnectionWrapper(sql, false);
            if (connectionWrapper != null) {
                sql = DataNodeHintParser.removeDataNodeIdHint(sql);
                // 如果当前已经有连接,则不做任何重试。对于更新来说，不管有没有事务，
                // 用户总期望getConnection获得连接之后，后续的一系列操作都在这同一个库，同一个连接上执行
                return executeBatch(connectionWrapper);
            } else {
                DataSourceIndexAndNeedRetryIfDailed dataSourceIndexAndNeedRetryIfDailed = DataNodeHintParser.convertHint2Index(sql);
                sql = DataNodeHintParser.removeDataNodeIdHint(sql);
                if (dataSourceIndexAndNeedRetryIfDailed == null) {
                    dataSourceIndexAndNeedRetryIfDailed = ThreadLocalDataSourceIndex.getIndex();
                }
                return datasourceManager.getSelector(false).getConnectionWrapperAndExecuteAndRetryIfFailed(null, executeBatchExecutor, retryingTimes, dataSourceIndexAndNeedRetryIfDailed);
            }
        } finally {
            if (indexToSetParameterMethodAndArgsMapList != null) {
                indexToSetParameterMethodAndArgsMapList.clear();
            }
        }
    }

    private int[] executeBatch(ConnectionWrapper connectionWrapper) throws SQLException {
        PreparedStatementWrapper preparedStatementWrapper = createPreparedStatementWrapper(connectionWrapper, sql);
        java.sql.PreparedStatement preparedStatement = (java.sql.PreparedStatement) preparedStatementWrapper.statement;
        for (Map<Integer, SetParameterMethodAndArgs> indexToSetParameterMethodAndArgsMap : indexToSetParameterMethodAndArgsMapList) {
            setBatchParameters(preparedStatement, indexToSetParameterMethodAndArgsMap.values());
            preparedStatement.addBatch();
        }
        return preparedStatement.executeBatch();
    }

    public void clearParameters() throws SQLException {
        indexToSetParameterMethodAndArgsMap.clear();
    }


}
